package com.yubest.fs.service.impl;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectRequest;
import com.yubest.fs.bean.*;
import com.yubest.fs.config.FileStorageProperties;
import com.yubest.fs.consts.Consts;
import com.yubest.fs.exception.StorageException;
import com.yubest.fs.util.FileUtil;
import lombok.Data;
import lombok.experimental.Accessors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * @Author hweiyu
 * @Description
 * @Date 2022/2/9 17:48
 */
public class AliyunStorageImpl extends AbstractStorage<AliyunStorageImpl.AliyunClient> {

    private static final Logger log = LoggerFactory.getLogger(AliyunStorageImpl.class);

    @Override
    public void prepareClient(String bucket) {
        //校验配置信息是否完整
        if (null == getProps() || null == getProps().getAliyun()) {
            throw new StorageException("aliyun configuration is missing");
        }
        FileStorageProperties.Config config = this.getProps().getAliyun();
        List<FileStorageProperties.Bucket> buckets = config.getBuckets();
        if (CollectionUtils.isEmpty(buckets)) {
            throw new StorageException("aliyun buckets configuration is missing");
        }
        if (StringUtils.isEmpty(config.getAccessKey()) || StringUtils.isEmpty(config.getAccessSecret())) {
            throw new StorageException("aliyun accesKey, accessSecret configuration is missing");
        }
        //选择使用的桶，如果没传入，则使用默认的桶
        String useBucket = StringUtils.isEmpty(bucket) ? config.getPrimaryBucket() : bucket;
        FileStorageProperties.Bucket bucketInfo;
        //如果没指定桶，则使用第一个
        if (null == useBucket) {
            bucketInfo = buckets.get(0);
        } else {
            bucketInfo = buckets.stream()
                    .filter(e -> Objects.equals(useBucket, e.getBucket()))
                    .findFirst()
                    .orElseThrow(() -> new StorageException("aliyun bucket:[" + useBucket + "] is missing"));
        }
        if (null == bucketInfo.getEndPoint()) {
            throw new StorageException("aliyun, fetch end point error");
        }
        //创建客户端
        AliyunClient aliyunClient = new AliyunClient()
                .setBucketInfo(bucketInfo)
                .setClient(new OSSClientBuilder().build(bucketInfo.getEndPoint(),
                        config.getAccessKey(),
                        config.getAccessSecret()));
        setStorageClient(aliyunClient);
    }

    @Override
    public void closeClient() {
        //关闭并清除客户端
        getStorageClient().getClient().shutdown();
        clearStorageClient();
    }

    @Override
    public PutResponse upload(PutFileRequest request) {
        PutResponse uploader = new PutResponse();

        OSS myClient = getStorageClient().getClient();
        FileStorageProperties.Bucket myBucket = getStorageClient().getBucketInfo();

        String path = getPath(myBucket.getPrefix(), request.getFileName());
        try {
            ObjectMetadata metadata = new ObjectMetadata();
            //封装metadata，设置用户自定义meta
            FileMeta fileMeta = new FileMeta()
                    .addAll(request.getMeta())
                    .add(Consts.META_FILE_NAME, !StringUtils.isEmpty(request.getFileName()) ? request.getFileName() : "")
                    .add(Consts.META_FILE_CONTENT_TYPE, !StringUtils.isEmpty(request.getContentType()) ? request.getContentType() : "")
                    .add(Consts.META_FILE_SIZE, String.valueOf(request.getPayload().length));
            metadata.setUserMetadata(fileMeta);
            PutObjectRequest putRequest = new PutObjectRequest(
                    myBucket.getBucket(), path, new ByteArrayInputStream(request.getPayload()), metadata);
            myClient.putObject(putRequest);
            uploader.setPath(path);
            uploader.setUrl(this.getAccessUrl(new GetFileRequest().setPath(path)));
        } catch (Exception e){
            if (log.isErrorEnabled()) {
                log.error("upload file error, filePath:{}", path, e);
            }
            uploader.setCode(Consts.CODE_ERROR);
            uploader.setError(e);
        }
        return uploader;
    }

    @Override
    public FileStream download(GetFileRequest request) {
        OSS myClient = getStorageClient().getClient();
        FileStorageProperties.Bucket myBucket = getStorageClient().getBucketInfo();
        InputStream is = null;
        try(ByteArrayOutputStream os = new ByteArrayOutputStream()) {
            OSSObject ossObject = myClient.getObject(myBucket.getBucket(), request.getPath());
            is = ossObject.getObjectContent();
            StreamUtils.copy(is, os);
            FileMeta meta = doGetMeta(request);
            return new FileStream(meta, os.toByteArray());
        } catch (Exception e) {
            if (log.isErrorEnabled()) {
                log.error("download file error, filePath:{}", request.getPath(), e);
            }
            throw new StorageException("download file error, file path:" + request.getPath());
        } finally {
            FileUtil.close(is);
        }
    }

    @Override
    public String getAccessUrl(GetFileRequest request) {
        OSS myClient = getStorageClient().getClient();
        FileStorageProperties.Bucket myBucket = getStorageClient().getBucketInfo();
        try {
            String rawUrl = myBucket.getDomain() + "/" + request.getPath();
            String url;
            //如果是私有的桶，封装时效性访问url
            if (Consts.PRIVATE_STORAGE.equals(myBucket.getType())) {
                GeneratePresignedUrlRequest urlRequest =
                        new GeneratePresignedUrlRequest(myBucket.getBucket(), request.getPath());
                urlRequest.setExpiration(new Date(new Date().getTime() + myBucket.getExpiresTime() * 1000));
                URL objectUrl = myClient.generatePresignedUrl(urlRequest);
                url = objectUrl.toString();
            } else {
                url = rawUrl;
            }
            return url;
        } catch (Exception e) {
            if (log.isErrorEnabled()) {
                log.error("fetch file access path error, file path:{}", request.getPath(), e);
            }
            throw new StorageException("fetch file access path error, file path:" + request.getPath());
        }
    }

    @Override
    public FileMeta doGetMeta(GetFileRequest request) {
        OSS myClient = getStorageClient().getClient();
        FileStorageProperties.Bucket myBucket = getStorageClient().getBucketInfo();
        try {
            ObjectMetadata meta = myClient.getObjectMetadata(myBucket.getBucket(), request.getPath());
            return new FileMeta().addAll(meta.getUserMetadata());
        } catch (Exception e) {
            if (log.isErrorEnabled()) {
                log.error("fetch file metadata error, filePath:{}", request.getPath(), e);
            }
            throw new StorageException("fetch file metadata error, file path:" + request.getPath());
        }
    }

    @Override
    public boolean isFileExists(GetFileRequest request) {
        OSS myClient = getStorageClient().getClient();
        FileStorageProperties.Bucket myBucket = getStorageClient().getBucketInfo();
        try {
            return myClient.doesObjectExist(myBucket.getBucket(), request.getPath());
        } catch (Exception e) {
            if (log.isErrorEnabled()) {
                log.error("does file exist error, filePath:{}", request.getPath(), e);
            }
            throw new StorageException("does file exist error, file path:" + request.getPath());
        }
    }

    @Override
    public boolean delete(RemoveFileRequest request) {
        OSS myClient = getStorageClient().getClient();
        FileStorageProperties.Bucket myBucket = getStorageClient().getBucketInfo();
        try {
            myClient.deleteObject(myBucket.getBucket(), request.getPath());
            return true;
        } catch (Exception e) {
            if (log.isErrorEnabled()) {
                log.error("delete file error, file path:{}", request.getPath(), e);
            }
            throw new StorageException("delete file error, file path:" + request.getPath());
        }
    }

    @Data
    @Accessors(chain = true)
    static class AliyunClient {

        private OSS client;

        private FileStorageProperties.Bucket bucketInfo;
    }

}
